package main

import (
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

var players = make(map[*websocket.Conn]*Player) //connected palyers
var question Question

var rooms = make(map[string]*Room)

var (
	upgrader = websocket.Upgrader{
		// solve crossdomain
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
)

type Data struct {
	Question string `json:"question"`
	Answer   string `json:"answer"`
}

//Message message json object
type Message struct {
	Category string `json:"category"`
	Message  string `json:"message"`
	Data     Data   `json:"data"`
}

//Player player
type Player struct {
	Name        string
	Score       int
	RoomID      string
	CurrStatus  string
	CurrAnswer  string
	ElapsedTime int64
}

//Question question object
type Question struct {
	Question string
	Answer   string
	SendTime int64
}

type Room struct {
	PlayerMap    map[string]*Player
	MaxPlayerNum int
	Status       string
}

func hello(c echo.Context) error {
	ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
	if err != nil {
		return err
	}
	defer ws.Close()

	var msg Message
	var data Data

	//find room which is not full
	//if all room is full, create one

	//check amount of player
	if len(players) < 2 {
		var player Player
		player.Score = 0
		players[ws] = &player

		//only support two players,more connections will be refuse
		if len(players) == 1 {
			msg.Category = "wait_player"
			msg.Message = "wait for another player"
			msg.Data = data
			broadcast(&msg)
		} else if len(players) == 2 {
			msg.Category = "match_complete"
			msg.Message = "completed"
			msg.Data = data
			broadcast(&msg)
		}

	} else {
		msg.Category = "player_full"
		msg.Message = "player is already full,please wait"
		msg.Data = data

		err = ws.WriteJSON(msg)
		if err != nil {
			log.Printf("error: %v", err)
		}
	}

	for {
		var reqMsg Message
		err = ws.ReadJSON(&reqMsg)
		if err != nil {
			c.Logger().Error(err)
		}

		switch cat := reqMsg.Category; cat {
		//server send question to client
		case "get_question":
			players[ws].CurrStatus = "get_question"
			getQuestion()
		//client send answer to server
		case "send_answer":
			players[ws].CurrStatus = "send_answer"
			players[ws].CurrAnswer = reqMsg.Data.Answer
			players[ws].ElapsedTime = time.Now().UnixNano() - question.SendTime
			checkAnswer(&reqMsg)
		default:
		}
	}
}

func getQuestion() {
	if checkCurrStatus("get_question") {
		var msg Message
		rand.Seed(time.Now().UnixNano())
		msg.Category = "send_question"
		msg.Message = ""
		msg.Data.Question = RandStringRunes(3)
		question.Question = msg.Data.Question
		question.Answer = msg.Data.Question
		question.SendTime = time.Now().UnixNano()
		broadcast(&msg)
	}
}

func checkAnswer(reqMsg *Message) {
	if checkCurrStatus("send_answer") {
		var msg Message
		var fastTime int64
		var winner *Player
		var winConn *websocket.Conn
		msg.Category = "send_result"
		//set a big enough init time
		fastTime = 99999999999
		for conn, player := range players {
			msg.Data.Answer = question.Answer
			if player.CurrAnswer == question.Answer {
				msg.Message = "your answer is right, your score is " + strconv.Itoa(player.Score)
				if player.ElapsedTime < fastTime || fastTime == 99999999999 {
					fastTime = player.ElapsedTime
					ptmp := player
					ctmp := conn
					winner = ptmp
					winConn = ctmp
				}
				// player.Score = player.Score + 1
			} else {
				msg.Message = "your answer is wrong"
			}
			conn.WriteJSON(&msg)
		}
		//get winner and send notify message
		if winner != nil {
			winner.Score = winner.Score + 1
			msg.Category = "send_notify"
			msg.Message = "your win this round, your score is " + strconv.Itoa(winner.Score)
			winConn.WriteJSON(&msg)
		} else {
			//if no winner, notice all players
			msg.Category = "send_notify"
			msg.Message = "no one win this round"
			broadcast(&msg)
		}

	}
}

func broadcast(msg *Message) {
	for player := range players {
		err := player.WriteJSON(msg)
		if err != nil {
			log.Printf("error: %v", err)
			player.Close()
			delete(players, player)
		}
	}
}

//checkCurrStatus ensure all players in same satatus then send msg
func checkCurrStatus(status string) bool {
	for _, player := range players {
		if status != player.CurrStatus {
			return false
		}
	}
	return true
}

//RandStringRunes get a random string
func RandStringRunes(n int) string {
	rand.Seed(time.Now().UnixNano())
	var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func main() {
	e := echo.New()
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())
	e.Static("/", "../public")
	e.GET("/ws", hello)
	e.Logger.Fatal(e.Start(":1323"))
}
